1use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
24use crate::copp::copp2::formulation::Copp2Problem;
25use crate::copp::copp2::opt2::clarabel_constraints::{
26 clarabel_standard_capacity_topp2, clarabel_standard_constraint_topp2,
27};
28use crate::copp::{ClarabelOptions, CoppObjective, clarabel_to_copp2_solution};
29use crate::diag::{
30 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
31 format_duration_human,
32};
33use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
34use clarabel::algebra::CscMatrix;
35use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
36use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
37use core::f64;
38use itertools::{Itertools, izip};
39use nalgebra::{DMatrix, DVectorView};
40
41#[cfg(test)]
42use crate::copp::copp2::stable::basic::a_to_b_topp2;
43
44pub fn copp2_socp<'a, M: RobotTorque>(
65 problem: &Copp2Problem<'a, M>,
66 options: &ClarabelOptions,
67) -> Result<Vec<f64>, CoppError> {
68 let (a_profile, solution) = copp2_socp_expert(problem, options)?;
69 a_profile.ok_or_else(|| CoppError::ClarabelSolverStatus("copp2_socp".into(), solution.status))
70}
71
72pub fn copp2_socp_expert<'a, M: RobotTorque>(
101 problem: &Copp2Problem<'a, M>,
102 options: &ClarabelOptions,
103) -> Result<(Option<Vec<f64>>, DefaultSolution<f64>), CoppError> {
104 match options.verbosity() {
105 Verbosity::Silent => copp2_socp_core(problem, (options, SilentVerboser)),
106 Verbosity::Summary => copp2_socp_core(problem, (options, SummaryVerboser::new())),
107 Verbosity::Debug => copp2_socp_core(problem, (options, DebugVerboser::new())),
108 Verbosity::Trace => copp2_socp_core(problem, (options, TraceVerboser::new())),
109 }
110}
111
112fn copp2_socp_core<'a, M: RobotTorque>(
124 problem: &Copp2Problem<'a, M>,
125 options_verboser: (&ClarabelOptions, impl Verboser),
126) -> Result<(Option<Vec<f64>>, DefaultSolution<f64>), CoppError> {
127 let (options, mut verboser) = options_verboser;
128 let (idx_s_start, idx_s_final) = problem.idx_s_interval;
129 if verboser.is_enabled(Verbosity::Summary) {
130 verboser.record_start_time();
131 crate::verbosity_log!(
132 crate::diag::Verbosity::Summary,
133 "\ncopp2_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
134 idx_s_start,
135 idx_s_final,
136 problem.objectives.len(),
137 problem.s_len()
138 );
139 }
140 if verboser.is_enabled(Verbosity::Trace) {
141 let settings = options.clarabel_settings();
142 crate::verbosity_log!(
143 crate::diag::Verbosity::Summary,
144 "copp2_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
145 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
146 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
147 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
148 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
149 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
150 settings.tol_gap_rel,
151 settings.tol_feas,
152 settings.max_iter,
153 settings.verbose
154 );
155 }
156 let n = idx_s_final - idx_s_start;
158 let (cap_val_std, cap_b_std, cap_cone_std) =
162 clarabel_standard_capacity_topp2(&problem.robot.constraints, problem.idx_s_interval);
163 let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
164 clarabel_objective_capacity_copp2(n, problem.objectives, problem.robot);
165 if verboser.is_enabled(Verbosity::Debug) {
166 crate::verbosity_log!(
167 crate::diag::Verbosity::Summary,
168 "copp2_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
169 );
170 }
171 let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
174 let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
175 let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
176 let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
177 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
178 if verboser.is_enabled(Verbosity::Trace) {
179 crate::verbosity_log!(
180 crate::diag::Verbosity::Summary,
181 "copp2_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
182 cap_val_std + cap_val_obj,
183 cap_val_std + cap_val_obj,
184 cap_val_std + cap_val_obj,
185 cap_b_std + cap_b_obj,
186 cap_cone_std + cap_cone_obj
187 );
188 }
189 let row_before_std = row.len();
191 let col_before_std = col.len();
192 let val_before_std = val.len();
193 let b_before_std = b.len();
194 let cones_before_std = cones.len();
195 clarabel_standard_constraint_topp2(
196 &problem.as_topp2_problem(),
197 (&mut row, &mut col, &mut val, &mut b, &mut cones),
198 &verboser,
199 );
200 if verboser.is_enabled(Verbosity::Trace) {
201 crate::verbosity_log!(
202 crate::diag::Verbosity::Summary,
203 "copp2_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
204 row.len() - row_before_std,
205 col.len() - col_before_std,
206 val.len() - val_before_std,
207 b.len() - b_before_std,
208 cones.len() - cones_before_std
209 );
210 }
211 let row_before_sqrt = row.len();
214 let col_before_sqrt = col.len();
215 let val_before_sqrt = val.len();
216 let b_before_sqrt = b.len();
217 let cones_before_sqrt = cones.len();
218 let n_var_old = clarabel_sqrt_a_copp2(
219 n,
220 problem.objectives,
221 (&mut row, &mut col, &mut val, &mut b, &mut cones),
222 );
223 if verboser.is_enabled(Verbosity::Trace) {
224 crate::verbosity_log!(
225 crate::diag::Verbosity::Summary,
226 "copp2_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
227 row.len() - row_before_sqrt,
228 col.len() - col_before_sqrt,
229 val.len() - val_before_sqrt,
230 b.len() - b_before_sqrt,
231 cones.len() - cones_before_sqrt,
232 n_var_old
233 );
234 }
235 let mut q_object = Vec::<f64>::with_capacity(n_vars);
236 q_object.resize(n_var_old, 0.0);
237 let row_before_obj = row.len();
239 let col_before_obj = col.len();
240 let val_before_obj = val.len();
241 let b_before_obj = b.len();
242 let cones_before_obj = cones.len();
243 let q_before_obj = q_object.len();
244 clarable_objective_copp2(
245 problem,
246 (
247 &mut row,
248 &mut col,
249 &mut val,
250 &mut b,
251 &mut cones,
252 &mut q_object,
253 ),
254 )?;
255 if verboser.is_enabled(Verbosity::Trace) {
256 let (q_min, q_max) = q_object
257 .iter()
258 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
259 (mn.min(v), mx.max(v))
260 });
261 crate::verbosity_log!(
262 crate::diag::Verbosity::Summary,
263 "copp2_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
264 row.len() - row_before_obj,
265 col.len() - col_before_obj,
266 val.len() - val_before_obj,
267 b.len() - b_before_obj,
268 cones.len() - cones_before_obj,
269 q_object.len() - q_before_obj,
270 q_min,
271 q_max
272 );
273 }
274 if verboser.is_enabled(Verbosity::Debug) {
275 crate::verbosity_log!(
276 crate::diag::Verbosity::Summary,
277 "copp2_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
278 row.len(),
279 col.len(),
280 val.len(),
281 b.len(),
282 cones.len(),
283 q_object.len()
284 );
285 }
286 if verboser.is_enabled(Verbosity::Summary) {
287 crate::verbosity_log!(
288 crate::diag::Verbosity::Summary,
289 "copp2_socp: ready to solve with row/col/val/b/cones = {}/{}/{}/{}/{} and n_var = {}.",
290 row.len(),
291 col.len(),
292 val.len(),
293 b.len(),
294 cones.len(),
295 q_object.len()
296 );
297 }
298 let n_var = q_object.len();
300 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
301 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
302 if verboser.is_enabled(Verbosity::Trace) {
303 crate::verbosity_log!(
304 crate::diag::Verbosity::Summary,
305 "copp2_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
306 b.len(),
307 n_var,
308 a_csc.nnz(),
309 p_object.nnz()
310 );
311 }
312 let settings = options.clarabel_settings().clone();
314 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
315 .map_err(|e| CoppError::ClarabelSolverError("copp2_socp".into(), e))?;
316 solver.solve();
317 let solution = solver.solution;
318 if verboser.is_enabled(Verbosity::Summary) {
319 crate::verbosity_log!(
320 crate::diag::Verbosity::Summary,
321 "copp2_socp: solve done, status = {:?}, elapsed = {}.",
322 solution.status,
323 format_duration_human(verboser.elapsed())
324 );
325 }
326 if verboser.is_enabled(Verbosity::Trace) {
327 let show = solution.x.len().min(3);
328 crate::verbosity_log!(
329 crate::diag::Verbosity::Summary,
330 "copp2_socp: solution x_len={}, head={:?}",
331 solution.x.len(),
332 &solution.x[0..show]
333 );
334 }
335 let a_profile = if options.is_allow(solution.status) {
336 Some(clarabel_to_copp2_solution(problem.s_len(), &solution))
337 } else {
338 None
339 };
340 if verboser.is_enabled(Verbosity::Trace) {
341 crate::verbosity_log!(
342 crate::diag::Verbosity::Summary,
343 "copp2_socp: allow(status)={}, extracted_profile={}",
344 options.is_allow(solution.status),
345 if a_profile.is_some() {
346 "Some(a)"
347 } else {
348 "None"
349 }
350 );
351 }
352 Ok((a_profile, solution))
353}
354
355fn clarabel_objective_capacity_copp2<M: RobotBasic>(
357 n: usize,
358 objective: &[CoppObjective],
359 robot: &Robot<M>,
360) -> (usize, usize, usize, usize) {
361 let flag_need_eta = objective.iter().any(|obj| {
362 matches!(
363 obj,
364 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
365 )
366 });
367 let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) = if flag_need_eta {
370 (4 * (n + 1), 4 * (n + 1), n + 2, 2 * (n + 1))
371 } else {
372 (0, 0, 0, n + 1)
373 };
374 let dim = robot.dim();
376 for obj in objective {
377 match obj {
378 CoppObjective::Time(_) => {
379 capacity_val += 6 * n;
381 capacity_b += 3 * n;
382 capacity_cones += n;
383 n_vars += n;
384 }
385 CoppObjective::ThermalEnergy(_, _) => {
386 capacity_val += (6 + 2 * dim) * n;
388 capacity_b += (dim + 2) * n;
389 capacity_cones += n;
390 n_vars += n;
391 }
392 CoppObjective::TotalVariationTorque(_, _) => {
393 capacity_val += 8 * dim * n;
395 capacity_b += 2 * dim * n;
396 capacity_cones += 1;
397 n_vars += dim * n;
398 }
399 _ => {}
400 }
401 }
402 (capacity_val, capacity_b, capacity_cones, n_vars)
403}
404
405fn clarabel_sqrt_a_copp2(
411 n: usize,
412 objective: &[CoppObjective],
413 constraints: ConstraintsClarabel,
414) -> usize {
415 let (row, col, val, b, cones) = constraints;
416 for obj in objective {
417 match obj {
418 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
419 row.extend(b.len()..b.len() + n + 1);
422 col.extend((n + 1)..(2 * (n + 1)));
423 val.resize(val.len() + n + 1, -1.0);
424 b.resize(b.len() + n + 1, 0.0);
425 cones.push(NonnegativeConeT(n + 1));
426 row.extend(b.len()..b.len() + 3 * (n + 1));
431 val.resize(val.len() + 3 * (n + 1), -1.0);
432 cones.resize(cones.len() + n + 1, SecondOrderConeT(3));
433 for k in 0..=n {
434 col.extend([k, k, k + n + 1]);
436 b.extend([0.25, -0.25, 0.0]);
438 }
440 return 2 * (n + 1);
441 }
442 _ => {}
443 }
444 }
445 n + 1
446}
447
448fn clarabel_objective_time_copp2(
451 s: &[f64],
452 weight: f64,
453 objective_constraints: ObjConsClarabel,
454) -> bool {
455 if weight < 0.0 {
456 return false;
457 }
458 let (row, col, val, b, cones, q_object) = objective_constraints;
459 let weight = 8.0 * weight;
463 let n_var_old = q_object.len();
464 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
466 let len = s.len();
470 for k in 0..(len - 1) {
471 row.resize(row.len() + 3, b.len());
473 col.extend([len + k, len + k + 1, n_var_old + k]);
474 val.extend([-1.0, -1.0, -1.0]);
475 b.push(0.0);
476 row.resize(row.len() + 3, b.len());
478 col.extend([len + k, len + k + 1, n_var_old + k]);
479 val.extend([-1.0, -1.0, 1.0]);
480 b.push(0.0);
481 b.push(1.0);
483 }
485 cones.resize(cones.len() + len - 1, SecondOrderConeT(3));
486 true
487}
488
489fn clarabel_objective_thermal_energy_copp2(
492 s: &[f64],
493 weight: f64,
494 normalize: &[f64],
495 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
496 objective_constraints: ObjConsClarabel,
497) -> bool {
498 if weight < 0.0 {
499 return false;
500 }
501 let (row, col, val, b, cones, q_object) = objective_constraints;
502 let len = s.len();
506 let mut coeff_a_curr = coeffs_torque.0.clone();
507 let mut coeff_a_next = coeffs_torque.1.clone();
508 let mut coeff_g = coeffs_torque.2.clone();
509 let weight = 8.0 * weight;
511 let n_var_old = q_object.len();
512 q_object.extend(s.windows(2).map(|s_pair| weight * (s_pair[1] - s_pair[0])));
514 let dim = coeff_a_curr.nrows();
516 if normalize.len() != dim {
518 return false;
519 }
520 let normalize = DVectorView::from_slice(normalize, dim);
521 for mut col in coeff_a_curr.column_iter_mut() {
522 col.component_mul_assign(&normalize);
523 }
524 for mut col in coeff_a_next.column_iter_mut() {
525 col.component_mul_assign(&normalize);
526 }
527 for mut col in coeff_g.column_iter_mut() {
528 col.component_mul_assign(&normalize);
529 }
530 for (k, (col_a_curr, col_a_next, col_g)) in izip!(
535 coeff_a_curr.column_iter(),
536 coeff_a_next.column_iter(),
537 coeff_g.column_iter()
538 )
539 .enumerate()
540 {
541 row.resize(row.len() + 3, b.len());
543 col.extend([len + k, len + k + 1, n_var_old + k]);
544 val.extend([-1.0, -1.0, -1.0]);
545 b.push(0.0);
546 row.resize(row.len() + 3, b.len());
548 col.extend([len + k, len + k + 1, n_var_old + k]);
549 val.extend([-1.0, -1.0, 1.0]);
550 b.push(0.0);
551 for (&v_a_curr, &v_a_next, &v_g) in
553 izip!(col_a_curr.iter(), col_a_next.iter(), col_g.iter())
554 {
555 row.resize(row.len() + 2, b.len());
556 col.extend([k, k + 1]);
557 val.extend([v_a_curr, v_a_next]);
558 b.push(v_g);
559 }
560 }
561 cones.resize(cones.len() + len - 1, SecondOrderConeT(dim + 2));
562 true
563}
564
565fn clarabel_objective_tv_torque_copp2(
568 weight: f64,
569 normalize: &[f64],
570 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
571 objective_constraints: ObjConsClarabel,
572) -> bool {
573 if weight < 0.0 {
574 return false;
575 }
576 let (row, col, val, b, cones, q_object) = objective_constraints;
577 let mut coeff_a_curr = coeffs_torque.0.clone();
580 let mut coeff_a_next = coeffs_torque.1.clone();
581 let mut coeff_g = coeffs_torque.2.clone();
582 let dim = coeff_a_curr.nrows();
583 if normalize.len() != dim {
584 return false;
585 }
586 let normalize = DVectorView::from_slice(normalize, dim);
588 for mut col in coeff_a_curr.column_iter_mut() {
589 col.component_mul_assign(&normalize);
590 }
591 for mut col in coeff_a_next.column_iter_mut() {
592 col.component_mul_assign(&normalize);
593 }
594 for mut col in coeff_g.column_iter_mut() {
595 col.component_mul_assign(&normalize);
596 }
597 let n_b_old = b.len();
602 let mut buffer0 = vec![0.0; dim];
605 let mut buffer1 = vec![0.0; dim];
606 for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
607 coeff_a_curr.column_iter(),
608 coeff_a_next.column_iter(),
609 coeff_g.column_iter()
610 )
611 .tuple_windows()
612 .enumerate()
613 {
614 buffer0.clear();
616 buffer1.clear();
617 buffer0.extend(
618 col_a_next
619 .iter()
620 .zip(col_b_curr.iter())
621 .map(|(&v_a_next, &v_b_curr)| v_b_curr - v_a_next),
622 );
623 buffer1.extend(
624 col_g_curr
625 .iter()
626 .zip(col_g_next.iter())
627 .map(|(&v_g_curr, &v_g_next)| v_g_curr - v_g_next),
628 );
629 let n_var_old = q_object.len();
632 for (i, (&v0, &v1, &v_a_curr, &v_b_next)) in izip!(
633 buffer0.iter(),
634 buffer1.iter(),
635 col_a_curr.iter(),
636 col_b_next.iter()
637 )
638 .enumerate()
639 {
640 row.resize(row.len() + 4, b.len());
643 col.extend([k, k + 1, k + 2, n_var_old + i]);
644 val.extend([-v_a_curr, v0, v_b_next, -1.0]);
645 b.push(v1);
646
647 row.resize(row.len() + 4, b.len());
650 col.extend([k, k + 1, k + 2, n_var_old + i]);
651 val.extend([v_a_curr, -v0, -v_b_next, -1.0]);
652 b.push(-v1);
653 }
654
655 q_object.resize(q_object.len() + dim, weight);
657 }
658 cones.push(NonnegativeConeT(b.len() - n_b_old));
659 true
660}
661
662fn clarabel_objective_linear_copp2(
664 s: &[f64],
665 weight: f64,
666 alpha: &[f64],
667 beta: &[f64],
668 q_object: &mut [f64],
669) -> bool {
670 if alpha.len() != s.len() || beta.len() != s.len() - 1 {
671 return false;
672 }
673 for (va, q) in alpha.iter().zip(q_object.iter_mut()) {
676 *q += weight * va;
678 }
679 for (s_pair, vb, q_curr) in izip!(s.windows(2), beta.iter(), q_object.iter_mut()) {
680 *q_curr -= 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
682 }
683 for (s_pair, vb, q_next) in izip!(s.windows(2), beta.iter(), q_object.iter_mut().skip(1)) {
684 *q_next += 0.5 * weight * vb / (s_pair[1] - s_pair[0]);
686 }
687 true
688}
689
690fn clarable_objective_copp2<M: RobotTorque>(
691 problem: &Copp2Problem<M>,
692 objective_constraints: ObjConsClarabel,
693) -> Result<(), CoppError> {
694 let (row, col, val, b, cones, q_object) = objective_constraints;
695 let s = problem
696 .robot
697 .constraints
698 .s_vec(problem.idx_s_interval.0, problem.idx_s_interval.1 + 1)?;
699 let coeffs_torque = if problem.objectives.iter().any(|obj| {
700 matches!(
701 obj,
702 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
703 )
704 }) {
705 problem.robot.torque2_coeff_a(
707 problem.idx_s_interval.0,
708 problem.idx_s_interval.1 - problem.idx_s_interval.0,
709 )
710 } else {
711 (
712 DMatrix::<f64>::zeros(0, 0),
713 DMatrix::<f64>::zeros(0, 0),
714 DMatrix::<f64>::zeros(0, 0),
715 )
716 };
717 for obj in problem.objectives {
718 match obj {
719 CoppObjective::Time(weight) => {
720 if !clarabel_objective_time_copp2(&s, *weight, (row, col, val, b, cones, q_object))
721 {
722 return Err(CoppError::InvalidInput(
723 "copp2_socp".into(),
724 "Invalid Time objective.".into(),
725 ));
726 }
727 }
728 CoppObjective::ThermalEnergy(weight, normalize) => {
729 if !clarabel_objective_thermal_energy_copp2(
730 &s,
731 *weight,
732 normalize,
733 &coeffs_torque,
734 (row, col, val, b, cones, q_object),
735 ) {
736 return Err(CoppError::InvalidInput(
737 "copp2_socp".into(),
738 "Invalid ThermalEnergy objective.".into(),
739 ));
740 }
741 }
742 CoppObjective::TotalVariationTorque(weight, normalize) => {
743 if !clarabel_objective_tv_torque_copp2(
744 *weight,
745 normalize,
746 &coeffs_torque,
747 (row, col, val, b, cones, q_object),
748 ) {
749 return Err(CoppError::InvalidInput(
750 "copp2_socp".into(),
751 "Invalid TotalVariationTorque objective.".into(),
752 ));
753 }
754 }
755 CoppObjective::Linear(weight, alpha, beta) => {
756 if !clarabel_objective_linear_copp2(&s, *weight, alpha, beta, q_object) {
757 return Err(CoppError::InvalidInput(
758 "copp2_socp".into(),
759 "Invalid Linear objective.".into(),
760 ));
761 }
762 }
763 }
764 }
765 Ok(())
766}
767
768#[cfg(test)]
770pub(crate) fn objective_value_copp2_opt<M: RobotTorque>(
771 robot: &Robot<M>,
772 start_idx_s: usize,
773 objective: &[CoppObjective],
774 a_profile: &[f64],
775) -> (f64, Vec<f64>) {
776 let Ok(s) = robot
777 .constraints
778 .s_vec(start_idx_s, start_idx_s + a_profile.len())
779 else {
780 return (f64::INFINITY, vec![0.0; objective.len()]);
781 };
782 if a_profile.len() != s.len() {
783 return (f64::INFINITY, vec![0.0; objective.len()]);
784 }
785 let b_profile = a_to_b_topp2(&s, a_profile);
786 let torque = if objective.iter().any(|obj| {
787 matches!(
788 obj,
789 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
790 )
791 }) {
792 let torque_result =
793 robot.get_torque_with_ab(&a_profile[0..a_profile.len() - 1], &b_profile, start_idx_s);
794 match torque_result {
795 Ok(torque) => torque,
796 _ => return (f64::INFINITY, vec![0.0; objective.len()]),
797 }
798 } else {
799 DMatrix::<f64>::zeros(0, 0)
800 };
801 let a_sqrt = if objective.iter().any(|obj| {
802 matches!(
803 obj,
804 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
805 )
806 }) {
807 a_profile.iter().map(|a| a.sqrt()).collect()
808 } else {
809 Vec::new()
810 };
811 let mut obj_val = Vec::with_capacity(objective.len());
812 let mut obj_val_total = 0.0;
813 for obj in objective {
814 match obj {
815 CoppObjective::Time(weight) => {
816 let obj_here = objective_value_time_copp2(&s, &a_sqrt);
817 obj_val.push(obj_here);
818 obj_val_total += weight * obj_here;
819 }
820 CoppObjective::ThermalEnergy(weight, normalize) => {
821 let obj_here =
822 objective_value_thermal_energy_copp2(&s, &a_sqrt, &torque, normalize);
823 obj_val.push(obj_here);
824 obj_val_total += weight * obj_here;
825 }
826 CoppObjective::TotalVariationTorque(weight, normalize) => {
827 let obj_here = objective_value_tv_torque_copp2(&torque, normalize);
828 obj_val.push(obj_here);
829 obj_val_total += weight * obj_here;
830 }
831 CoppObjective::Linear(weight, alpha, beta) => {
832 let obj_here = objective_value_linear_copp2(&s, a_profile, alpha, beta);
833 obj_val.push(obj_here);
834 obj_val_total += weight * obj_here;
835 }
836 }
837 }
838 (obj_val_total, obj_val)
839}
840
841#[cfg(test)]
844#[inline(always)]
845fn objective_value_time_copp2(s: &[f64], a_sqrt: &[f64]) -> f64 {
846 let mut objective = 0.0;
848 for (s_pair, a_sqrt_pair) in s.windows(2).zip(a_sqrt.windows(2)) {
849 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]);
850 }
851 2.0 * objective
852}
853
854#[cfg(test)]
856#[inline(always)]
857fn objective_value_thermal_energy_copp2(
858 s: &[f64],
859 a_sqrt: &[f64],
860 torque: &DMatrix<f64>,
861 normalize: &[f64],
862) -> f64 {
863 let mut objective = 0.0;
865 for (s_pair, a_sqrt_pair, torque_col) in
866 izip!(s.windows(2), a_sqrt.windows(2), torque.column_iter())
867 {
868 let mut sum = 0.0;
869 for (torque, normal) in torque_col.iter().zip(normalize.iter()) {
870 sum += (torque * normal).powi(2);
871 }
872 objective += (s_pair[1] - s_pair[0]) / (a_sqrt_pair[0] + a_sqrt_pair[1]) * sum;
873 }
874 2.0 * objective
875}
876
877#[cfg(test)]
879#[inline(always)]
880fn objective_value_tv_torque_copp2(torque: &DMatrix<f64>, normalize: &[f64]) -> f64 {
881 let mut objective = 0.0;
883 for (torque_col_curr, torque_col_next) in torque.column_iter().tuple_windows() {
884 for (torque_prev, torque_next, normal) in izip!(
885 torque_col_curr.iter(),
886 torque_col_next.iter(),
887 normalize.iter()
888 ) {
889 objective += (torque_next - torque_prev).abs() * normal;
890 }
891 }
892 objective
893}
894
895#[cfg(test)]
897#[inline(always)]
898fn objective_value_linear_copp2(s: &[f64], a_profile: &[f64], alpha: &[f64], beta: &[f64]) -> f64 {
899 let mut objective = 0.0;
902 for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
903 objective += a_curr * alpha_curr;
905 }
906 for (a_pair, s_pair, beta_curr) in izip!(a_profile.windows(2), s.windows(2), beta.iter()) {
907 objective += 0.5 * beta_curr * (a_pair[1] - a_pair[0]) / (s_pair[1] - s_pair[0]);
909 }
910 objective
911}
912
913#[cfg(test)]
914mod tests {
915 use super::*;
916 use crate::copp::copp2::stable::basic::{
917 Copp2ProblemBuilder, Topp2ProblemBuilder, s_to_t_topp2,
918 };
919 use crate::copp::copp2::stable::reach_set2::{ReachSet2Options, ReachSet2OptionsBuilder};
920 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
921 use crate::copp::{ClarabelOptions, ClarabelOptionsBuilder};
922 use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
923 use crate::robot::demo::Plannar2LinkEnd;
924 use crate::robot::robot_core::Robot;
925 use core::panic;
926 use std::time::Instant;
927 use std::vec;
928
929 #[test]
930 fn test_copp2_socp_only_time() -> Result<(), CoppError> {
931 run_test_copp2_socp_only_time_repeated(1, false)
932 }
933
934 #[test]
937 #[ignore = "slow"]
938 fn test_copp2_socp_only_time_robust() -> Result<(), CoppError> {
939 run_test_copp2_socp_only_time_repeated(100, true)?;
940 Ok(())
941 }
942
943 #[test]
944 fn test_copp2_socp() -> Result<(), CoppError> {
945 run_test_copp2_socp_repeated(1, false)
946 }
947
948 #[test]
956 #[ignore = "slow"]
957 fn test_copp2_socp_robust() -> Result<(), CoppError> {
958 run_test_copp2_socp_repeated(100, true)?;
959 Ok(())
960 }
961
962 fn run_test_copp2_socp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
963 let options_socp = ClarabelOptionsBuilder::new()
964 .allow_almost_solved(true)
965 .build()?;
966
967 let mut tc_sum_case0 = 0.0;
968 let mut tc_sum_case1 = 0.0;
969 let mut tc_sum_case2 = 0.0;
970 let mut tc_sum_case3 = 0.0;
971 let mut tc_sum_case4 = 0.0;
972 let mut obj_sum_case0 = vec![0.0; 4];
973 let mut obj_sum_case1 = vec![0.0; 4];
974 let mut obj_sum_case2 = vec![0.0; 4];
975 let mut obj_sum_case3 = vec![0.0; 4];
976 let mut obj_sum_case4 = vec![0.0; 4];
977
978 for i_exp in 0..n_exp {
979 let n: usize = 1000;
980 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
981 let dim = robot.dim();
982
983 let mut rng = rand::rng();
984 let (s, derivs, omega, phi) =
985 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
986 robot.with_s(&s.as_view())?;
987 robot.with_q(
988 &derivs.q.as_view(),
989 &derivs.dq.as_ref().unwrap().as_view(),
990 &derivs.ddq.as_ref().unwrap().as_view(),
991 derivs.dddq.as_ref().map(|m| m.as_view()).as_ref(),
992 0,
993 )?;
994 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
995
996 let objectives_test = [
998 CoppObjective::Time(1.0),
999 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1000 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1001 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1002 ];
1003 let a_feasible = vec![0.0; n];
1004
1005 let mut copp2_problem = Copp2ProblemBuilder::new(
1007 &robot,
1008 (0, n - 1),
1009 (0.0, 0.0),
1010 &[CoppObjective::Time(1.0)],
1011 )
1012 .build()?;
1013 let start = Instant::now();
1014 let mut a_case0 = copp2_socp(&copp2_problem, &options_socp)?;
1015 let tc_copp2_case0 = start.elapsed().as_secs_f64() * 1E3;
1016 robot
1017 .constraints
1018 .project_to_feasible_topp2(&mut a_case0, &a_feasible, 0)?;
1019 let (_, obj_case0) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case0);
1020
1021 let obj_case1 = [
1023 CoppObjective::Time(1.0),
1024 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1025 ];
1026 copp2_problem.objectives = &obj_case1;
1027 let start = Instant::now();
1028 let mut a_case1 = copp2_socp(&copp2_problem, &options_socp)?;
1029 let tc_copp2_case1 = start.elapsed().as_secs_f64() * 1E3;
1030 robot
1031 .constraints
1032 .project_to_feasible_topp2(&mut a_case1, &a_feasible, 0)?;
1033 let (_, obj_case1) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case1);
1034 if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1035 let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0);
1036 let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0);
1037 crate::verbosity_log!(
1038 crate::diag::Verbosity::Summary,
1039 "omega = {omega:?}\nphi = {phi:?}"
1040 );
1041 crate::verbosity_log!(
1042 crate::diag::Verbosity::Summary,
1043 "Case 0: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1044 obj_case0[0],
1045 obj_case0[1],
1046 tf_case0
1047 );
1048 crate::verbosity_log!(
1049 crate::diag::Verbosity::Summary,
1050 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, tf = {:.6}",
1051 obj_case1[0],
1052 obj_case1[1],
1053 tf_case1
1054 );
1055 crate::verbosity_log!(
1056 crate::diag::Verbosity::Summary,
1057 "Interesting... Cases 0 and 1"
1058 );
1059 }
1060
1061 let obj_case2 = [
1063 CoppObjective::Time(1.0),
1064 CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1065 ];
1066 copp2_problem.objectives = &obj_case2;
1067 let start = Instant::now();
1068 let mut a_case2 = copp2_socp(&copp2_problem, &options_socp)?;
1069 let tc_copp2_case2 = start.elapsed().as_secs_f64() * 1E3;
1070 robot
1071 .constraints
1072 .project_to_feasible_topp2(&mut a_case2, &a_feasible, 0)?;
1073 let (_, obj_case2) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case2);
1074 if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1075 crate::verbosity_log!(
1076 crate::diag::Verbosity::Summary,
1077 "omega = {omega:?}\nphi = {phi:?}"
1078 );
1079 crate::verbosity_log!(
1080 crate::diag::Verbosity::Summary,
1081 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1082 obj_case1[0],
1083 obj_case1[1]
1084 );
1085 crate::verbosity_log!(
1086 crate::diag::Verbosity::Summary,
1087 "Case 2: obj_time = {:.6}, obj_thermal_energy = {:.6}",
1088 obj_case2[0],
1089 obj_case2[1]
1090 );
1091 crate::verbosity_log!(
1092 crate::diag::Verbosity::Summary,
1093 "Interesting... Cases 1 and 2"
1094 );
1095 }
1096
1097 let obj_case3 = [
1099 CoppObjective::Time(1.0),
1100 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1101 ];
1102 copp2_problem.objectives = &obj_case3;
1103 let start = Instant::now();
1104 let mut a_case3 = copp2_socp(&copp2_problem, &options_socp)?;
1105 let tc_copp2_case3 = start.elapsed().as_secs_f64() * 1E3;
1106 robot
1107 .constraints
1108 .project_to_feasible_topp2(&mut a_case3, &a_feasible, 0)?;
1109 let (_, obj_case3) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case3);
1110 if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1111 crate::verbosity_log!(
1112 crate::diag::Verbosity::Summary,
1113 "omega = {omega:?}\nphi = {phi:?}"
1114 );
1115 crate::verbosity_log!(
1116 crate::diag::Verbosity::Summary,
1117 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1118 obj_case1[0],
1119 obj_case1[1],
1120 obj_case1[2]
1121 );
1122 crate::verbosity_log!(
1123 crate::diag::Verbosity::Summary,
1124 "Case 3: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_total_variation_torque = {:.6}",
1125 obj_case3[0],
1126 obj_case3[1],
1127 obj_case3[2]
1128 );
1129 crate::verbosity_log!(
1130 crate::diag::Verbosity::Summary,
1131 "Interesting... Cases 1 and 3"
1132 );
1133 }
1134
1135 let obj_case4 = [
1137 CoppObjective::Time(1.0),
1138 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1139 ];
1140 copp2_problem.objectives = &obj_case4;
1141 let start = Instant::now();
1142 let mut a_case4 = copp2_socp(&copp2_problem, &options_socp)?;
1143 let tc_copp2_case4 = start.elapsed().as_secs_f64() * 1E3;
1144 robot
1145 .constraints
1146 .project_to_feasible_topp2(&mut a_case4, &a_feasible, 0)?;
1147 let (_, obj_case4) = objective_value_copp2_opt(&robot, 0, &objectives_test, &a_case4);
1148 if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
1149 crate::verbosity_log!(
1150 crate::diag::Verbosity::Summary,
1151 "omega = {omega:?}\nphi = {phi:?}"
1152 );
1153 crate::verbosity_log!(
1154 crate::diag::Verbosity::Summary,
1155 "Case 1: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1156 obj_case1[0],
1157 obj_case1[1],
1158 obj_case1[3]
1159 );
1160 crate::verbosity_log!(
1161 crate::diag::Verbosity::Summary,
1162 "Case 4: obj_time = {:.6}, obj_thermal_energy = {:.6}, obj_linear = {:.6}",
1163 obj_case4[0],
1164 obj_case4[1],
1165 obj_case4[3]
1166 );
1167 crate::verbosity_log!(
1168 crate::diag::Verbosity::Summary,
1169 "Interesting... Cases 1 and 4"
1170 );
1171 }
1172 if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
1173 crate::verbosity_log!(
1174 crate::diag::Verbosity::Summary,
1175 "omega = {omega:?}\nphi = {phi:?}"
1176 );
1177 crate::verbosity_log!(
1178 crate::diag::Verbosity::Summary,
1179 "Case 2: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1180 obj_case2[0],
1181 obj_case2[2],
1182 obj_case2[3]
1183 );
1184 crate::verbosity_log!(
1185 crate::diag::Verbosity::Summary,
1186 "Case 4: obj_time = {:.6}, obj_total_variation_torque = {:.6}, obj_linear = {:.6}",
1187 obj_case4[0],
1188 obj_case4[2],
1189 obj_case4[3]
1190 );
1191 crate::verbosity_log!(
1192 crate::diag::Verbosity::Summary,
1193 "Interesting... Cases 2 and 4"
1194 );
1195 }
1196
1197 if flag_print_step {
1198 crate::verbosity_log!(
1199 crate::diag::Verbosity::Summary,
1200 "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1201 i_exp + 1,
1202 tc_copp2_case0,
1203 obj_case0,
1204 tc_copp2_case1,
1205 obj_case1,
1206 tc_copp2_case2,
1207 obj_case2,
1208 tc_copp2_case3,
1209 obj_case3,
1210 tc_copp2_case4,
1211 obj_case4
1212 );
1213 }
1214
1215 tc_sum_case0 += tc_copp2_case0;
1216 tc_sum_case1 += tc_copp2_case1;
1217 tc_sum_case2 += tc_copp2_case2;
1218 tc_sum_case3 += tc_copp2_case3;
1219 tc_sum_case4 += tc_copp2_case4;
1220 for i in 0..obj_case0.len() {
1221 obj_sum_case0[i] += obj_case0[i];
1222 obj_sum_case1[i] += obj_case1[i];
1223 obj_sum_case2[i] += obj_case2[i];
1224 obj_sum_case3[i] += obj_case3[i];
1225 obj_sum_case4[i] += obj_case4[i];
1226 }
1227 }
1228
1229 for i in 0..4 {
1230 obj_sum_case0[i] /= n_exp as f64;
1231 obj_sum_case1[i] /= n_exp as f64;
1232 obj_sum_case2[i] /= n_exp as f64;
1233 obj_sum_case3[i] /= n_exp as f64;
1234 obj_sum_case4[i] /= n_exp as f64;
1235 }
1236
1237 crate::verbosity_log!(
1238 crate::diag::Verbosity::Summary,
1239 "Average {} experiments:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
1240 n_exp,
1241 tc_sum_case0 / n_exp as f64,
1242 obj_sum_case0,
1243 tc_sum_case1 / n_exp as f64,
1244 obj_sum_case1,
1245 tc_sum_case2 / n_exp as f64,
1246 obj_sum_case2,
1247 tc_sum_case3 / n_exp as f64,
1248 obj_sum_case3,
1249 tc_sum_case4 / n_exp as f64,
1250 obj_sum_case4
1251 );
1252
1253 Ok(())
1254 }
1255
1256 fn run_one_copp2_socp_only_time_case(
1257 options_ra: &ReachSet2Options,
1258 options_socp: &ClarabelOptions,
1259 ) -> Result<(f64, f64, f64, f64, f64, f64), CoppError> {
1260 let n: usize = 1000;
1261 let mut robot = Robot::with_capacity(Plannar2LinkEnd::new(1.0, 1.0, 1.0, 1.0), n);
1262 let dim = robot.dim();
1263
1264 let mut rng = rand::rng();
1265 let (s, derivs, omega, phi) =
1266 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1267 robot.with_s(&s.as_view())?;
1268 robot.with_q(
1269 &derivs.q.as_view(),
1270 &derivs.dq.as_ref().unwrap().as_view(),
1271 &derivs.ddq.as_ref().unwrap().as_view(),
1272 derivs.dddq.as_ref().map(|m| m.as_view()).as_ref(),
1273 0,
1274 )?;
1275 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, None)?;
1276
1277 let topp2_problem =
1278 Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1279 let start = Instant::now();
1280 let a_ra = topp2_ra(&topp2_problem, options_ra)?;
1281 let tc_ra = start.elapsed().as_secs_f64() * 1E3;
1282 let (tf_ra, _) = s_to_t_topp2(s.as_slice(), &a_ra, 0.0);
1283
1284 let obj1 = [CoppObjective::Linear(
1285 1.0,
1286 &vec![-1.0; n],
1287 &vec![0.0; n - 1],
1288 )];
1289 let mut copp2_problem =
1290 Copp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0), &obj1).build()?;
1291 let start = Instant::now();
1292 let a_lp = copp2_socp(&copp2_problem, options_socp)?;
1293 let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1294 let (tf_lp, _) = s_to_t_topp2(s.as_slice(), &a_lp, 0.0);
1295
1296 copp2_problem.objectives = &[CoppObjective::Time(1.0)];
1297 let start = Instant::now();
1298 let a_qp = copp2_socp(&copp2_problem, options_socp)?;
1299 let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1300 let (tf_qp, _) = s_to_t_topp2(s.as_slice(), &a_qp, 0.0);
1301
1302 if (tf_lp - tf_ra).abs() > 1e-3 || (tf_qp - tf_ra).abs() > 1e-3 {
1303 crate::verbosity_log!(
1304 crate::diag::Verbosity::Summary,
1305 "omega = {omega:?}\nphi = {phi:?}"
1306 );
1307 panic!("COPP2 time optimality failed!");
1308 }
1309
1310 Ok((tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp))
1311 }
1312
1313 fn run_test_copp2_socp_only_time_repeated(
1314 n_exp: usize,
1315 flag_print_step: bool,
1316 ) -> Result<(), CoppError> {
1317 let mut tc_sum_ra = 0.0;
1318 let mut tc_sum_lp = 0.0;
1319 let mut tc_sum_qp = 0.0;
1320 let mut tf_sum_ra = 0.0;
1321 let mut tf_sum_lp = 0.0;
1322 let mut tf_sum_qp = 0.0;
1323
1324 let options_ra = ReachSet2OptionsBuilder::new()
1325 .lp_feas_tol(1E-9)
1326 .a_cmp_abs_tol(1E-9)
1327 .a_cmp_rel_tol(1E-9)
1328 .build()?;
1329 let options_socp = ClarabelOptionsBuilder::new()
1330 .allow_almost_solved(true)
1331 .build()?;
1332
1333 for i_exp in 0..n_exp {
1334 let (tc_ra, tc_lp, tc_qp, tf_ra, tf_lp, tf_qp) =
1335 run_one_copp2_socp_only_time_case(&options_ra, &options_socp)?;
1336
1337 if flag_print_step {
1338 crate::verbosity_log!(
1339 crate::diag::Verbosity::Summary,
1340 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1341 i_exp + 1,
1342 tc_ra,
1343 tc_lp,
1344 tc_qp,
1345 tf_ra,
1346 tf_lp,
1347 tf_qp,
1348 );
1349 }
1350
1351 tc_sum_ra += tc_ra;
1352 tc_sum_lp += tc_lp;
1353 tc_sum_qp += tc_qp;
1354 tf_sum_ra += tf_ra;
1355 tf_sum_lp += tf_lp;
1356 tf_sum_qp += tf_qp;
1357 }
1358
1359 crate::verbosity_log!(
1360 crate::diag::Verbosity::Summary,
1361 "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_qp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_qp = {:.6}",
1362 n_exp,
1363 tc_sum_ra / n_exp as f64,
1364 tc_sum_lp / n_exp as f64,
1365 tc_sum_qp / n_exp as f64,
1366 tf_sum_ra / n_exp as f64,
1367 tf_sum_lp / n_exp as f64,
1368 tf_sum_qp / n_exp as f64
1369 );
1370
1371 Ok(())
1372 }
1373}